package main

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"os"
	"sort"
	"time"

	"github.com/urfave/cli/v2"
)

type Prize struct {
	Name   string `json:"name"`
	Weigth int    `json:"Weigth"`
}

var (
	weigth_sum int
	prizePool  []*Prize
)

func main() {
	rand.Seed(time.Now().UnixNano())

	app := cli.NewApp()
	app.Name = "lottery"
	app.Usage = "Lucky draw program"
	app.Version = "1.2.3"
	app.Commands = []*cli.Command{
		drawCmd,
		statisticalCmd,
	}

	err := app.Run(os.Args)
	if err != nil {
		panic(err)
	}
}

var drawCmd = &cli.Command{
	Name:  "draw",
	Usage: "Draw a lottery or raffle",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:  "pool",
			Usage: "Set the prize pool file",
			Value: "prize-pool.json",
		},
		&cli.IntFlag{
			Name:  "count",
			Usage: "Set lottery number",
			Value: 1,
		},
	},
	Action: func(ctx *cli.Context) error {
		return drawOperation(ctx, true)
	},
}

var statisticalCmd = &cli.Command{
	Name:  "probability",
	Usage: "Statistical lottery probability",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:  "pool",
			Usage: "Set the prize pool file",
			Value: "prize-pool.json",
		},
		&cli.IntFlag{
			Name:  "count",
			Usage: "Set lottery number",
			Value: 10000,
		},
	},
	Action: func(ctx *cli.Context) error {
		return drawOperation(ctx, false)
	},
}

func drawOperation(ctx *cli.Context, draw bool) error {
	pool := ctx.String("pool")
	count := ctx.Int("count")

	if err := parsePool(pool); err != nil {
		return err
	}

	mm := make(map[string]int)
	for i := 0; i < count; i++ {
		_, prize := drawPrize()
		if prize == nil {
			prize = &Prize{"谢谢", 0}
		}

		if draw {
			fmt.Println("恭喜您，抽中了 【", prize.Name, "】！")
		} else {
			v, ok := mm[prize.Name]
			if !ok {
				v = 0
			}

			mm[prize.Name] = v + 1
		}
	}

	if !draw {
		println("=============================================")
		println("prize\t\t   count\tprobability")
		println("---------------------------------------------")
		vs := NewValSorter(mm)
		vs.Sort()

		for _, k := range vs.Keys {
			fmt.Printf("%s\t\t%8d\t%.8f\n", k, mm[k], float32(mm[k])/float32(count))
		}
	}

	return nil
}

func parsePool(poolfile string) error {
	file, err := os.Open(poolfile)
	if err != nil {
		return err
	}

	decoder := json.NewDecoder(file)
	if err = decoder.Decode(&prizePool); err != nil {
		return err
	}

	for _, prize := range prizePool {
		weigth_sum += prize.Weigth
	}

	return nil
}

func drawPrize() (int, *Prize) {
	rand_w := rand.Intn(weigth_sum)

	for i, prize := range prizePool {
		if rand_w <= prize.Weigth {
			return i, prize
		}

		rand_w -= prize.Weigth
	}

	return -1, nil
}

type ValSorter struct {
	Keys []string
	Vals []int
}

func NewValSorter(m map[string]int) *ValSorter {
	vs := &ValSorter{
		Keys: make([]string, 0, len(m)),
		Vals: make([]int, 0, len(m)),
	}
	for k, v := range m {
		vs.Keys = append(vs.Keys, k)
		vs.Vals = append(vs.Vals, v)
	}
	return vs
}

func (vs *ValSorter) Sort() {
	sort.Sort(vs)
}

func (vs *ValSorter) Len() int           { return len(vs.Vals) }
func (vs *ValSorter) Less(i, j int) bool { return vs.Vals[i] > vs.Vals[j] }
func (vs *ValSorter) Swap(i, j int) {
	vs.Vals[i], vs.Vals[j] = vs.Vals[j], vs.Vals[i]
	vs.Keys[i], vs.Keys[j] = vs.Keys[j], vs.Keys[i]
}
